package com.example.core.helper;

import com.example.common.config.*;
import com.example.common.constants.BasicConst;
import com.example.common.constants.GatewayConst;
import com.example.common.exception.ResponseException;
import com.example.core.context.GatewayContext;
import com.example.core.request.GatewayRequest;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import org.apache.commons.lang3.StringUtils;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

import static com.example.common.enums.ResponseCode.PATH_NO_MATCHED;

/**
 * @Author Peng Yisheng
 * @Date 2024/11/18 22:20
 * @Description 请求处理辅助类
 */
public class RequestHelper {
    public static GatewayContext doContext(FullHttpRequest request, ChannelHandlerContext ctx) {

        //	构建请求对象GatewayRequest
        GatewayRequest gateWayRequest = doRequest(request, ctx);

        //	根据请求对象头部的uniqueId，获取服务定义信息
        // TODO: 应该建立请求路径path到服务之间的映射，而不是根据请求头部来确定调用哪个服务
        ServiceDefinition serviceDefinition = DynamicConfigManager.getInstance().getServiceDefinition(gateWayRequest.getUniqueId());

        // TODO:没有可用服务时，serviceDefinition为null，应当失败

        //	构建GateWayContext对象
        //  从缓存中获取rule，根据路径匹配规则
        Rule rule = getRule(gateWayRequest, serviceDefinition.getServiceId());

        // TODO: 服务定义protocol字段使用枚举
        GatewayContext gatewayContext = new GatewayContext(
                serviceDefinition.getProtocol(),
                ctx,
                HttpUtil.isKeepAlive(request),
                gateWayRequest,
                rule);

//        // TODO:后续服务发现做完，这里都要改成动态的
//        // 已经在负载均衡过滤器中实现
//        gatewayContext.getRequest().setModifyHost("127.0.0.1:8080");

        return gatewayContext;
    }

    /**
     * 构建Request请求对象
     */
    private static GatewayRequest doRequest(FullHttpRequest fullHttpRequest, ChannelHandlerContext ctx) {

        HttpHeaders headers = fullHttpRequest.headers();
        //	从header头获取必须要传入的关键属性 uniqueId
        // TODO: 去掉该头部，不依赖该字段选择调用的服务，而应该使用路径映射服务
        String uniqueId = headers.get(GatewayConst.UNIQUE_ID);

        // 灰度流量：请求头部gray=true
        boolean gray = Boolean.parseBoolean(headers.get(GatewayConst.GRAY));

        String host = headers.get(HttpHeaderNames.HOST);
        HttpMethod method = fullHttpRequest.method();
        String uri = fullHttpRequest.uri();
        String clientIp = getClientIp(ctx, fullHttpRequest);
        String contentType = HttpUtil.getMimeType(fullHttpRequest) == null ? null :
                HttpUtil.getMimeType(fullHttpRequest).toString();
        Charset charset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.UTF_8);

        GatewayRequest gatewayRequest = new GatewayRequest(uniqueId,
                charset,
                clientIp,
                host,
                uri,
                method,
                contentType,
                headers,
                fullHttpRequest,
                gray);

        return gatewayRequest;
    }

    /**
     * 获取客户端ip
     */
    private static String getClientIp(ChannelHandlerContext ctx, FullHttpRequest request) {
        String xForwardedValue = request.headers().get(BasicConst.HTTP_FORWARD_SEPARATOR);

        String clientIp = null;
        if (StringUtils.isNotEmpty(xForwardedValue)) {
            List<String> values = Arrays.asList(xForwardedValue.split(", "));
            if (values.size() >= 1 && StringUtils.isNotBlank(values.get(0))) {
                clientIp = values.get(0);
            }
        }
        if (clientIp == null) {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
            clientIp = inetSocketAddress.getAddress().getHostAddress();
        }
        return clientIp;
    }

    /**
     * 通过serviceId获取Rule对象
     *
     * @param gateWayRequest
     * @param serviceId
     * @return
     */
    private static Rule getRule(GatewayRequest gateWayRequest, String serviceId) {
        String key = serviceId + "." + gateWayRequest.getPath();
        // 请求路径匹配规则
        // 请求路径只会唯一匹配一条规则
        Rule rule = DynamicConfigManager.getInstance().getRuleByPath(key);

        if (rule != null) {
            return rule;
        }

        // 如果找不到，先获取服务对应的规则列表，匹配请求路径前缀和规则路径前缀一致的规则
        // 服务可以对应多条规则，但是具体请求路径只能唯一匹配一条规则
        // TODO: r.完整路径 需要完全匹配 gatewayRequest.getPath，目前只是匹配前缀
        return DynamicConfigManager.getInstance().getRuleByServiceId(serviceId)
                .stream().filter(r -> gateWayRequest.getPath().startsWith(r.getPrefix()))
                .findAny().orElseThrow(() -> new ResponseException(PATH_NO_MATCHED));
    }
}
